iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
Modern Web

Phoenix 1.7 完全教學系列 第 9

9 Context

  • 分享至 

  • xImage
  •  

在上一篇我們建立了 Schema Gratitude.Notes.Note,現在我們要在 Gratitude.Notes 這個 Context 裡面把資料庫操作包裝起來,之後處理畫面邏輯時可以直接呼叫使用。

設想 Context

我們先盤點一下我們所需要的功能

  1. 讀取全部的 Note
  2. 建立 Note
  3. 刪除 Note
  4. 更新 Note

再預想一下我們之後會想要怎麼使用這些功能

alias Gratitude.Notes

# 讀取全部的 Note
Gratitude.Notes.list_notes()

# 建立 Note
Gratitude.Notes.create_note(%{content: "出門有帶傘"})

# 刪除 Note
Gratitude.Notes.delete_note!(id)

# 更新 Note
Gratitude.Notes.update_note!(id, %{content: "媽媽有提醒我帶傘"})

好了之後就開始吧

建立 Context

在新增檔案 lib/gratitude/notes.ex,並且定義 Gratitude.Notes 模組

記得 alias Gratitude.RepoGratitude.Notes.Note 這樣我們可以直接使用 RepoNote 這兩個 module ,讓這邊看起來簡潔一些

defmodule Gratitude.Notes do
  alias Gratitude.Repo
  alias Gratitude.Notes.Note
end

讀取全部的 Note list_notes/0

這個非常簡單,前一篇我們已經在 iex 裡面使用過了,使用 Repo.all/1

def list_notes do
  Repo.all(Note)
end

(雖然我們在這邊直接給他 Note,其實他收的參數是 Queryable,之後有比較複雜的查詢時,會再一起說明。)

建立 Note create_note/1

如果使用 Repo.insert/1 存進資料庫的話,在內部使用的話沒關係,但是我們主要是要從網頁上傳送使用者填寫的資料,所以我們必須要使用在建立 Schema 時一起建立的 changeset/2 函式。

def create_note(attrs) do
  %Note{}
  |> Note.changeset(attrs)
  |> Repo.insert()
end

changeset (改變集)函式顧名思義就是紀錄了這次改變了什麼,但因為我們這次是新增,所以我們給他一個空的 Note struct ,再給他這次的輸入 attrs,就會回傳一個 changeset,再把這個 changeset 丟給 Repo.insert/1 就可以了,我們來在 iex -S mix 試試看。

alias Gratitude.Notes
alias Gratitude.Notes.Note

Notes.create_note(%{content: "沒帶到錢包,還好口袋有一百塊可以搭車")
#=>
{:ok, %Gr...省略}

還記得我們的 note 有檢查必需要提供 content 欄位的內容嗎?試試看如果我們沒有給的情況:

Notes.create_note(%{})
#=>
{:error,
 #Ecto.Changeset<
   action: :insert,
   changes: %{},
   errors: [content: {"can't be blank", [validation: :required]}],
   data: #Gratitude.Notes.Note<>,
   valid?: false
 >}

這個是經典的錯誤 tuple 第一個是 :error,第二個是我們的 changeset ,我們可以看到他的 errors 紀錄了 content 的錯誤,說明 content 欄位不能為空。

所以我們剛剛建立的 create_note/1,在之後的畫面邏輯中,就可以搭配 case 來決定要回覆使用者建立成功或是失敗的訊息。

更新 Note update_note!/2

再剛剛新增的時候,我們使用空的 Note struct 與新資料給 Note.changset/2 來建立 changeset,可以想像更新的時候,我們可以使用舊的資料,再跟新的改變一起給 Note.changset/2 來產生改變用的 changeset。

# 我們可以使用 id 拿到現有的 note 資料
note = Repo.get(Note, 1)

# 再把新的資料給 changeset
Note.changeset(note, %{content: "媽媽有提醒我帶傘"})
|> Repo.update()

# 最後呼叫 Repo.update/1 就可以更新資料了

依照上面的作法,我們可以實作 update_note/2 函式,
不過拿現有的資料跟更新他應該是兩件事,我們通常會把 get_note/1 獨立出來。

def get_note(id) do
  Repo.get(Note, id)
end

def update_note(note, attrs) do
  note
  |> Note.changeset(attrs)
  |> Repo.update()
end

小技巧:如果我們的 iex -S mix 還沒有關掉的話,會發現新寫的函式還沒有 compile 所以不在裡面,但是如果我們把 iex 關掉,新開一個的話又要重新打剛剛的 alias,這個時候可以直接執行 recompile 來重新 compile 所有的檔案,就可以不用重開了。

來試試看剛剛寫的 update_note/2

note = Notes.get_note(1)
Notes.update_note(note, %{content: "媽媽有提醒我帶傘還有穿雨鞋"})

刪除 Note delete_note!/1

刪除的話就比較簡單了,直接給 Repo.delete/1 就可以了。不過使用時跟剛剛一樣,我們要先找到要刪除的資料。

def delete_note(note) do
  Repo.delete(note)
end

這邊可能會想到,這個函式這麼小,是不是可以省略,到時候要使用的時候直接呼叫 Repo.delete(note) 就可以了?
在自己試玩的專案或許可以,但是如果考慮到維護性的話,我們還是建議把把資料處理的邏輯與畫面分開。這些操作都包在一個 module 裡面,這樣之後如果要修改的話,例如要刪除 note 的時候會寄信通知作者,這樣我們只要修改這個 module 就可以了。

來試試看剛剛寫的 delete_note/1

note = Notes.get_note(1)
Notes.delete_note(note)

# 這個時候我們再試著找找看 id 為 1 的 note,會發現已經找不到了
# 回傳的是 nil
Notes.get_note(1)
#=> nil

上一篇
8 Schema
下一篇
10 Context 測試
系列文
Phoenix 1.7 完全教學30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言